某天正式站一個查詢功能整天回 500。本機重現:完全正常。測試:全綠。程式碼 diff 看了三遍:沒有任何可疑之處。這種「本機好好的、正式站死了」的事故,是雙資料庫架構(Day 2)欠的債找上門了。
正式站的 Python 驅動程式(psycopg2 這一系)的參數化查詢,用的佔位符語法是 %s:
cur.execute("SELECT * FROM items WHERE id = %s", (item_id,))
驅動程式在送出 SQL 前會解析字串裡所有的 %。問題來了:如果你的 SQL 本身含有字面上的 %——最常見的兩個場景:
# 模糊搜尋
cur.execute("SELECT * FROM items WHERE name LIKE '%關鍵字%' AND type = %s", (t,))
# 日期格式化
cur.execute("SELECT strftime('%Y-%m', created_at), COUNT(*) FROM logs WHERE user = %s", (u,))
驅動程式會試圖把 '%關'、'%Y' 也解析成佔位符,解析失敗,整條查詢炸掉。
而本機的 DuckDB 用的是 ? 佔位符,字面 % 對它毫無意義,所以本機測試永遠是綠的。這條地雷的殺傷力就在這裡:你所有的安全網(本機測試、CI)都偵測不到它,第一個發現的人是正式站的使用者。
治本的做法是在連線層加一道轉換(db.py 裡的 SQL 轉換函式),把要送去正式站的 SQL 統一處理:字面 % 轉義成 %%、? 佔位符轉成 %s。所有 SQL 都走這一層,個別開發者(跟 AI agent)不需要記得手動轉義。
但光有防線不夠,這條雷的教訓有三層:
第一層:技術教訓。 含字面 % 又帶參數的 SQL 是高危組合,寫的當下就要意識到。
第二層:流程教訓。 「本機全綠」在雙資料庫架構下不是安全證明,只是「本機方言下正確」的證明。部署後的健康檢查(Day 27)因此變成必要而不是加分項。
第三層:協作教訓。 這條進了 CLAUDE.md 地雷清單之後,agent 每次寫到 LIKE 或日期格式化都會主動確認轉義——這正是 Day 3 說的「把肌肉記憶外部化」的實例。
這條雷後來在另一個完全不相關的功能裡復發過——同樣是字面 %,另一段被複製貼上過的程式碼。當下的體會變成另一條通則寫進了文件:複製貼上型的 bug 一定成對出現,查到一處,必須順手搜一次整個專案還有沒有同樣形狀的地方。 這件事交給 agent 做特別合適:「找出專案裡所有含字面 % 且帶參數的 SQL」是一句話的事。